﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace Riba.MVVMSL
{
	///<summary>
	///<para>The NavigatorControl is a UserControl which provides many features that the <see cref="ViewModelBase"/> uses.</para>
	///<para>This UserControl has two Containers. You can navigate between these by populating them with own controls and by giving an AnimationMode. The NavigatorControl is fully templatable, so you can define own storyboards in styles.</para>
	///</summary>
	///<remarks>
	///<para>With NavigatorControl all major Menu and navigation needs of a MVVM Business Application can be covered.</para>
	///<para>Refer to the documentation for a deep tutorial on the usage of the features.</para>
	///</remarks>
	[TemplatePart(Name = "ContentControl1", Type = typeof(ContentControl))]
	[TemplatePart(Name = "ContentControl2", Type = typeof(ContentControl))]
	[TemplateVisualState(GroupName = "TransitionStates", Name = "Initial")]
	[TemplateVisualState(GroupName = "TransitionStates", Name = "EndAnimationForward")]
	[TemplateVisualState(GroupName = "TransitionStates", Name = "EndAnimationBackward")]
	public class NavigatorControl : Control
	{
		#region properties

		//public KeyTime AnimationDuration
		//{
		//  get { return (KeyTime)GetValue(AnimationDurationProperty); }
		//  set { SetValue(AnimationDurationProperty, value); }
		//}

		//public static readonly DependencyProperty AnimationDurationProperty =
		//  DependencyProperty.Register("AnimationDuration", typeof(KeyTime), typeof(NavigatorControl), new PropertyMetadata(KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(500))));


		///<summary>
		///<para>This is the comfort version if you don't want to use the SetValue()-Syntax. Set this Property to (typeof(YourWishedDefaultView)).AssemblyQualifiedName to fill the NavigatorControl with an initial Control at instantiation.</para>
		///</summary>
		///<example>TestGUI.Page1, TestGUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</example>
		///<remarks>
		///<para>You can define a property in your ViewModel that gets the AssemblyQualifiedName of YourWishedDefaultView and bind to it in XAML.</para>
		///<para>This way the magic string is avoided and you don't have to change it when AssemblyQualifiedName of YourWishedDefaultView changes (i.e. version update).</para>
		///</remarks>
		///<see cref="DefaultViewProperty"/>
		///<value>null</value>
		public string DefaultView
		{
			get { return (string)GetValue(DefaultViewProperty); }
			set { SetValue(DefaultViewProperty, value); }
		}

		///<summary>
		///<para>Set this Property to (typeof(YourWishedDefaultView)).AssemblyQualifiedName to fill the NavigatorControl with an initial Control at instantiation.</para>
		///</summary>
		///<example>TestGUI.Page1, TestGUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</example>
		///<remarks>
		///<para>You can define a property in your ViewModel that gets the AssemblyQualifiedName of YourWishedDefaultView and bind to it in XAML.</para>
		///<para>This way the magic string is avoided and you don't have to change it when AssemblyQualifiedName of YourWishedDefaultView changes (i.e. version update).</para>
		///</remarks>
		public static readonly DependencyProperty DefaultViewProperty =
			DependencyProperty.Register("DefaultView", typeof(string), typeof(NavigatorControl), new PropertyMetadata(null));

		///<summary>
		///<para>AnimationMode used in NavigateToView(FrameworkElement nextView) and for the DefaultView.</para>
		///</summary>
		///<value>AnimationMode.None</value>
		public AnimationMode DefaultAnimationMode
		{
			get { return (AnimationMode)GetValue(DefaultAnimationModeProperty); }
			set { SetValue(DefaultAnimationModeProperty, value); }
		}

		///<summary>
		///<para>AnimationMode used in NavigateToView(FrameworkElement nextView) and for the DefaultView</para>
		///</summary>
		public static readonly DependencyProperty DefaultAnimationModeProperty =
			DependencyProperty.Register("DefaultAnimationModeProperty", typeof(AnimationMode), typeof(NavigatorControl), new PropertyMetadata(AnimationMode.None));

		///<summary>
		///<para>Question used to ask if system should navigate away from a dirty dialog.</para>
		///</summary>
		///<value>null</value>
		public string IsDirtyQuestion
		{
			get { return (string)GetValue(IsDirtyQuestionProperty); }
			set { SetValue(IsDirtyQuestionProperty, value); }
		}

		///<summary>
		///<para>Question used to ask if system should navigate away from a dirty dialog.</para>
		///</summary>
		public static readonly DependencyProperty IsDirtyQuestionProperty =
			DependencyProperty.Register("IsDirtyQuestionProperty", typeof(string), typeof(NavigatorControl), new PropertyMetadata(null));

		///<summary>
		///<para>Caption in Question- Dialog. See IsDirtyQuestion.</para>
		///</summary>
		///<value>null</value>
		public string IsDirtyQuestionCaption
		{
			get { return (string)GetValue(IsDirtyQuestionCaptionProperty); }
			set { SetValue(IsDirtyQuestionCaptionProperty, value); }
		}

		///<summary>
		///<para>Caption in Question- Dialog. See IsDirtyQuestion.</para>
		///</summary>
		public static readonly DependencyProperty IsDirtyQuestionCaptionProperty =
			DependencyProperty.Register("IsDirtyQuestionCaptionProperty", typeof(string), typeof(NavigatorControl), new PropertyMetadata(null));

		///<summary>
		///<para>Caption of continue button in Question- Dialog.</para>
		///</summary>
		///<value>null</value>
		public string ContinueNavigationButtonCaption
		{
			get { return (string)GetValue(ContinueNavigationButtonCaptionProperty); }
			set { SetValue(ContinueNavigationButtonCaptionProperty, value); }
		}

		///<summary>
		///<para>Caption of continue button in Question- Dialog.</para>
		///</summary>
		public static readonly DependencyProperty ContinueNavigationButtonCaptionProperty =
			DependencyProperty.Register("ContinueNavigationButtonCaptionProperty", typeof(string), typeof(NavigatorControl), new PropertyMetadata(null));

		///<summary>
		///<para>Caption of cancel button in Question- Dialog.</para>
		///</summary>
		///<value>null</value>
		public string CancelNavigationButtonCaption
		{
			get { return (string)GetValue(CancelNavigationButtonCaptionProperty); }
			set { SetValue(CancelNavigationButtonCaptionProperty, value); }
		}

		///<summary>
		///<para>Caption of cancel button in Question- Dialog.</para>
		///</summary>
		public static readonly DependencyProperty CancelNavigationButtonCaptionProperty =
			DependencyProperty.Register("CancelNavigationButtonCaptionProperty", typeof(string), typeof(NavigatorControl), new PropertyMetadata(null));

		///<summary>
		///<para>Style</para>
		///</summary>
		///<value>null</value>
		public Style IsDirtyQuestionWindowStyle
		{
			get { return (Style)GetValue(IsDirtyQuestionWindowStyleProperty); }
			set { SetValue(IsDirtyQuestionWindowStyleProperty, value); }
		}

		///<summary>
		///<para>Style.</para>
		///</summary>
		public static readonly DependencyProperty IsDirtyQuestionWindowStyleProperty =
			DependencyProperty.Register("IsDirtyQuestionWindowStyleProperty", typeof(Style), typeof(NavigatorControl), new PropertyMetadata(null));

		#endregion properties

		#region private fields

		private ContentControl _contentControl1;
		private ContentControl _contentControl2;
		private FrameworkElement _previousView;
		private Type _previousPreviousViewType;
		private AnimationMode _animationMode;
		private FrameworkElement _currentView;

		#endregion private fields

		///<summary>
		///<para>Initializes a new instance of the <see cref="NavigatorControl"/> class.</para>
		///</summary>
		public NavigatorControl()
		{
			DefaultStyleKey = typeof(NavigatorControl);

			Loaded += (sender, e) =>
				{
					//Is not in Designer
					var c = DataContext as ViewModelBase;
					if (c != null)
						c.NavigatorLoad();
				};
		}

		///<summary>
		///<para>Control-specific logic. Not to be called from user-code!</para>
		///</summary>
		///<remarks>Has to be public because access rights of the base-implementation cannot be changed.</remarks>
		public sealed override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			_contentControl1 = GetTemplateChild("ContentControl1") as ContentControl;
			_contentControl2 = GetTemplateChild("ContentControl2") as ContentControl;

			if (DataContext == null || DesignerProperties.IsInDesignTool)
				return;

			ViewEventHandler.HandleNavigatorEvents(DataContext as ViewModelBase, this);

			// Show default View
			if (string.IsNullOrEmpty(DefaultView))
				return;

			NavigateToView(Type.GetType(DefaultView), DefaultAnimationMode);
		}

		private Type _nextViewType;
		private AnimationMode _nextAnimationMode;

		///<summary>
		///<para>Can be called from a Command and navigates between the ContentControls by using the defined AnimationMode.</para>
		///</summary>
		///<param name="nextViewType">Type of destination view to be visible at the end of the animation. null if you want to remove the current view.</param>
		///<param name="animationMode">Defines which AnimationMode should be used for the transition.</param>
		///<see cref="AnimationMode"/>
		public void NavigateToView(Type nextViewType, AnimationMode animationMode)
		{
			_nextViewType = nextViewType;
			_nextAnimationMode = animationMode;

			if (_currentView != null && _currentView.DataContext != null)
			{
				if (((ViewModelBase)_currentView.DataContext).IsDirty)
				{
					if (_previousView == null)
					{
						if (_nextViewType == null)
							return;
					}
					else
					{
						if (_previousView.GetType() == _nextViewType)
							return;
					}

					string message = IsDirtyQuestion ?? "You changed your data. Do you want to save them before?";
					string caption = IsDirtyQuestionCaption ?? "Save";
					string continueButton = ContinueNavigationButtonCaption ?? "Do not save";
					string cancelButton = CancelNavigationButtonCaption ?? "Go back to save";

					var buttons = new List<MessageWindowButton>();
					buttons.Add(new MessageWindowButton { Caption = continueButton, Tag = "continueNavigation" });
					buttons.Add(new MessageWindowButton { Caption = cancelButton, Tag = "cancelNavigation" });

					var messageWindow = new MessageWindow(message, caption, buttons, NavigateToView_Callback, IsDirtyQuestionWindowStyle);
					messageWindow.Show();

					return;
				}

				((ViewModelBase)_currentView.DataContext).IsDirty = false;
			}

			NavigateToView_Callback("continueNavigation");
		}

		private void NavigateToView_Callback(object tag)
		{
			if (_nextViewType == null)
			{
				_contentControl1.Content = null;
				_contentControl2.Content = null;
				_previousView = null;
				_currentView = null;
				_previousPreviousViewType = null;

				return;
			}

			if (tag.ToString() != "continueNavigation")
				return;

			if (_previousView != null)
			{
				if (_previousView.GetType() == _nextViewType)
					return;
			}

			ContentControl contentControlFrom;
			ContentControl contentControlTo;

			if (_nextAnimationMode == AnimationMode.Backward)
			{
				contentControlFrom = _contentControl2;
				contentControlTo = _contentControl1;
			}
			else
			{
				contentControlFrom = _contentControl1;
				contentControlTo = _contentControl2;
			}

			contentControlFrom.Content = null;
			contentControlTo.Content = null;

			if (_nextAnimationMode != AnimationMode.None)
				contentControlFrom.Content = _previousView;

			//Create new view for every navigate
			var nextView = Activator.CreateInstance(_nextViewType) as FrameworkElement;
			if (nextView == null)
				return;

			contentControlTo.Content = nextView;

			object exchangeData = null;
			if (_previousView != null)
			{
				exchangeData = ((ViewModelBase)_previousView.DataContext).ExchangeData;
				_previousPreviousViewType = _previousView.GetType();
			}
			else
				_previousPreviousViewType = null;

			_previousView = nextView;
			_currentView = nextView;

			((ViewModelBase)nextView.DataContext).NavigationComplete(exchangeData, (ViewModelBase)DataContext);

			if (_nextAnimationMode != AnimationMode.None)
				ChangeVisualState(_nextAnimationMode, true);
		}

		///<summary>
		///<para>Can be called from a Command in the ViewModel and navigates between the ContentControls by using the DefaultAnimationMode.</para>
		///</summary>
		///<param name="nextViewType">Type of destination view to be visible at the end of the animation.</param>
		///<see cref="DefaultAnimationMode"/>
		public void NavigateToView(Type nextViewType)
		{
			NavigateToView(nextViewType, DefaultAnimationMode);
		}

		///<summary>
		///<para>Can be called from a Command in the ViewModel and navigates automatically between the current ContentControl1 and ContentControl2 by also changing AnimationMode which creates a "swap-effect".</para>
		///</summary>
		public void NavigateAutoSwap()
		{
			if (_previousPreviousViewType == null)
				return;

			_animationMode = _animationMode == AnimationMode.Forward ? AnimationMode.Backward : AnimationMode.Forward;
			NavigateToView(_previousPreviousViewType, _animationMode);
		}

		private void ChangeVisualState(AnimationMode animationMode, bool useTransition)
		{
			if (animationMode == AnimationMode.Forward)
			{
				VisualStateManager.GoToState(this, "Initial", false);
				VisualStateManager.GoToState(this, "EndAnimationForward", useTransition);
			}
			else
			{
				VisualStateManager.GoToState(this, "Initial", false);
				VisualStateManager.GoToState(this, "EndAnimationBackward", useTransition);
			}
		}
	}
}
